/*
 * shawnbgu_local_topology.h
 *
 *  Created on: Apr 25, 2009
 *      Author: gal
 */

#ifndef SHAWNBGU_LOCAL_TOPOLOGY_H_
#define SHAWNBGU_LOCAL_TOPOLOGY_H_

#include "../../sys/logging/logger.h"
#include <map>
#include <vector>
#include <queue>
#include <iostream>

using namespace std;
using namespace shawn;

namespace shawnbgu {

//a data class for each node in the local bgu topology.
//should contain node_id, path to node, leadership number.

//TODO convert shawnbguNodeData into struct with typedef
class shawnbguNodeData {

public:

	int node_id_;
	int parent_id_;
	bool is_leader_;
	int leadership_;
	int leader_id_;


};


class ShawnbguNeighbourhoodData
#ifdef ENABLE_LOGGING
: public Logger
#endif
{

private:
	map<int, map<int, shawnbguNodeData> > neighbour_by_dist_;
	map<int, int> dist_by_neighbour_;



public:

	ShawnbguNeighbourhoodData()
	{}
	// ----------------------------------------------------------------------
	ShawnbguNeighbourhoodData(const ShawnbguNeighbourhoodData &other)
	{
		neighbour_by_dist_ = map<int, map<int, shawnbguNodeData> > (other.neighbour_by_dist_);
		dist_by_neighbour_ = map<int, int> (other.dist_by_neighbour_);
	}
	// ----------------------------------------------------------------------

	~ShawnbguNeighbourhoodData() {
	}
	// ----------------------------------------------------------------------
	bool insert(int node_dist, int node_id, shawnbguNodeData node_data) {

		map<int, int>::iterator found = dist_by_neighbour_.find(node_id);

		if (found == dist_by_neighbour_.end()) {
			add_new_value(node_dist, node_id, node_data);
			return true;
		} else if (node_dist <= found->second) {
			int found_dist= found->second;
			remove_old_value(found->second, node_id);
			add_new_value(node_dist, node_id, node_data);
			return true;
		} else {
			return false;
		}
	}

	// ----------------------------------------------------------------------

	void add_new_value(int node_dist, int node_id, shawnbguNodeData node_data) {

		dist_by_neighbour_.insert(make_pair(node_id, node_dist));
		map<int, map<int, shawnbguNodeData> >::iterator it =
				neighbour_by_dist_.find(node_dist);
		bool isEnd = it == neighbour_by_dist_.end();

		map<int, shawnbguNodeData> tmp = it->second;

		map<int, shawnbguNodeData>::iterator it_nodes;
		for (it_nodes = tmp.begin(); it_nodes != tmp.end(); it_nodes++) {
			int id = it_nodes->second.node_id_;
			/*INFO(logger(), "in the tmp map to be inserted in distance "
					<< node_dist
					<<"there is a node with id "
					<<id);*/
		}
		if (isEnd) {
			tmp.clear();
		}
		for (it_nodes = tmp.begin(); it_nodes != tmp.end(); it_nodes++) {
			int id = it_nodes->second.node_id_;
			/*INFO(logger(), "in the tmp map to be inserted in distance "
					<< node_dist
					<<"there is a node with id "
					<<id);*/
		}
		tmp.insert(make_pair(node_id, node_data));
		for (it_nodes = tmp.begin(); it_nodes != tmp.end(); it_nodes++) {
			int id = it_nodes->second.node_id_;
		/*	INFO(logger(), "in the tmp map to be inserted in distance "
					<< node_dist
					<<"there is a node with id "
					<<id);*/
		}

		neighbour_by_dist_.erase(node_dist);
		it = neighbour_by_dist_.find(node_dist);
		map<int, shawnbguNodeData> tmp2 = it->second;
		for (it_nodes = tmp2.begin(); it_nodes != tmp2.end(); it_nodes++) {
			int id = it_nodes->second.node_id_;
			/*INFO(logger(), "in the tmp map currently in distance "
					<< node_dist
					<<"there is a node with id "
					<<id);*/
		}
		neighbour_by_dist_.insert(make_pair(node_dist, tmp));
		it = neighbour_by_dist_.find(node_dist);
		tmp2 = it->second;
		for (it_nodes = tmp2.begin(); it_nodes != tmp2.end(); it_nodes++) {
			int id = it_nodes->second.node_id_;
			/*INFO(logger(), "in the tmp map currently in distance "
					<< node_dist
					<<"there is a node with id "
					<<id);*/
		}

	}
	// ----------------------------------------------------------------------

	void remove_old_value(int old_dist,int node_id){
		map<int,int>::iterator it = dist_by_neighbour_.find(node_id);
		//int dist = it ->second;
		dist_by_neighbour_.erase(node_id);
		/*it = dist_by_neighbour_.find(node_id);
		bool isEnd = it==dist_by_neighbour_.end();
		dist = it ->second;*/

		/*map<int,map<int,shawnbguNodeData> >::iterator it_nodes = neighbour_by_dist_.find(old_dist);
		isEnd = it_nodes==neighbour_by_dist_.end();
		int size = it_nodes->second.size();*/
		neighbour_by_dist_.find(old_dist)->second.erase(node_id);
		map<int,map<int,shawnbguNodeData> >::iterator it_nodes = neighbour_by_dist_.find(old_dist);
		//isEnd = it_nodes==neighbour_by_dist_.end();
		int size = it_nodes->second.size();
		if (size==0){
			neighbour_by_dist_.erase(old_dist);
		}
		/*
		it_nodes = neighbour_by_dist_.find(old_dist);
		isEnd = it_nodes==neighbour_by_dist_.end();*/
}

	// ----------------------------------------------------------------------

	map<int, shawnbguNodeData>& nodes_in_dist(int dist) {
		//map	<int,shawnbguNodeData> neighbour_by_dist_.find
		return neighbour_by_dist_.find(dist)->second;
}

// ----------------------------------------------------------------------

	bool nodes_in_dist_exist(int dist)
	{
		//TODO remove one level of indirection.
		//DEBUG( logger(), " entering ShawnbguNeighbourhoodData::nodes_in_dist_exist");

		/*int size = neighbour_by_dist_.find(dist)->second.size();
		return size>0;*/
		int size = neighbour_by_dist_.size();
		map<int,map<int,shawnbguNodeData> >::iterator it = neighbour_by_dist_.find(dist);
		bool isEnd = (it==neighbour_by_dist_.end());
		/*bool exist = !(neighbour_by_dist_.find(dist)->second.empty());
				//(neighbour_by_dist_.find(dist)!=neighbour_by_dist_.end());
		map<int,shawnbguNodeData> tmp = it->second;
		size = tmp.size();
		map<int,shawnbguNodeData>::iterator it_nodes = tmp.begin();
		shawnbguNodeData node_data = it_nodes->second;
		int node_id = node_data.node_id_;
		return exist;*/
		return !(isEnd);
	}

	// ----------------------------------------------------------------------

	shawnbguNodeData find_leaders()
	{
		if(neighbour_by_dist_.size()>1){
						int size = neighbour_by_dist_.size();
						int size2 = size;
						}

		map<int,map<int,shawnbguNodeData> >::iterator it;
		for(it=neighbour_by_dist_.begin(); it!=neighbour_by_dist_.end();it++)
		{

			int dist= it->first;


			if (it ->first==0){
				;
			}
			else{
				map<int,shawnbguNodeData> nodes = it->second;
				map<int,shawnbguNodeData> ::iterator nodes_it;
				for (nodes_it=nodes.begin();nodes_it!=nodes.end();nodes_it++){
					if (nodes_it->second.is_leader_){
						return nodes_it->second;
					}
				}

			}

		}
		shawnbguNodeData tmp = shawnbguNodeData();
		tmp.is_leader_=false;
		return tmp;

	}

};


// --------------------------------------------------------------------------------------------------------

class ShawnbguLocalTopology

#ifdef ENABLE_LOGGING
: public Logger
#endif

{
public:
	ShawnbguLocalTopology();
	//a copy constructor.

	ShawnbguLocalTopology(const ShawnbguLocalTopology&);

	ShawnbguLocalTopology(int, int);

	virtual ~ShawnbguLocalTopology();
	//getters
	int owner_id_w();
	int neighbourhood_data_w();


	bool nodes_in_dist_exist(int);
	map<int,shawnbguNodeData>& nodes_in_dist(int);



	void init(int, int,bool,int);
	void add(ShawnbguLocalTopology neighbour_topology);
	pair <int,int> find_leader();

private:
	int radius_;
	int owner_id_;
	shawnbguNodeData owner_data_;
	shawnbguNodeData* owner_data_w_;
	ShawnbguNeighbourhoodData neighbourhood_data_;

	//private methods
	queue<shawnbguNodeData> find_leaders();
	bool leaders_exist();
	void update_leader(shawnbguNodeData);



	};

}

#endif
